﻿using LibArchivumAeternum;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;

namespace ArchivumAeternumDesktop
{
    public class MainWindowViewModel : INotifyPropertyChanged
    {
        private string _configFile;
        private LibArchivumAeternum.Configuration _configuration;
        private ListEditorViewModel _tasks;
        private string _status;

        private int copied;
        private int linked;
        private int skipped;
        private int errors;
        private StringBuilder _log;
        public StringBuilder _shadow_log;

        public string ConfigFile
        {
            get
            {
                return _configFile;
            }

            set
            {
                if (value != _configFile)
                {
                    _configFile = value;

                    try
                    {
                        RegistryKey key;
                        key = Registry.CurrentUser.CreateSubKey("ArchivumAeternum");
                        key.SetValue("ConfigFile", value);
                        key.Close();
                    }
                    catch
                    {
                    }

                    NotifyPropertyChanged();
                }
            }
        }

        public string Status
        {
            get
            {
                return _status;
            }

            set
            {
                if (_status != value)
                {
                    _status = value;
                    NotifyPropertyChanged();
                }
            }
        }

        public string Destination
        {
            get
            {
                LibArchivumAeternum.Task task = Configuration.Tasks.Where(t => t.Name == Tasks.SelectedItem).FirstOrDefault();

                if (task != null)
                {
                    return task.Destination;
                }

                return null;
            }

            set
            {
                LibArchivumAeternum.Task task = Configuration.Tasks.Where(t => t.Name == Tasks.SelectedItem).FirstOrDefault();

                if (task != null)
                {
                    task.Destination = value;
                    NotifyPropertyChanged();
                }
            }
        }

        public bool ComparisonByFileSize
        {
            get
            {
                LibArchivumAeternum.Task task = Configuration.Tasks.Where(t => t.Name == Tasks.SelectedItem).FirstOrDefault();

                if (task == null)
                {
                    return false;
                }

                return task.EqualityCheck.HasFlag(EqualityCheck.Size);
            }

            set
            {
                LibArchivumAeternum.Task task = Configuration.Tasks.Where(t => t.Name == Tasks.SelectedItem).FirstOrDefault();

                if (task != null)
                {
                    task.EqualityCheck = (task.EqualityCheck & ~EqualityCheck.Size) | (value ? 0 : EqualityCheck.Size);
                    NotifyPropertyChanged();
                }
            }
        }

        public bool ComparisonByModificationDate
        {
            get
            {
                LibArchivumAeternum.Task task = Configuration.Tasks.Where(t => t.Name == Tasks.SelectedItem).FirstOrDefault();

                if (task == null)
                {
                    return false;
                }

                return task.EqualityCheck.HasFlag(EqualityCheck.ModificationDate);
            }

            set
            {
                LibArchivumAeternum.Task task = Configuration.Tasks.Where(t => t.Name == Tasks.SelectedItem).FirstOrDefault();

                if (task != null)
                {
                    task.EqualityCheck = (task.EqualityCheck & ~EqualityCheck.ModificationDate) | (value ? 0 : EqualityCheck.ModificationDate);
                    NotifyPropertyChanged();
                }
            }
        }

        public bool ComparisonByCheckSum
        {
            get
            {
                LibArchivumAeternum.Task task = Configuration.Tasks.Where(t => t.Name == Tasks.SelectedItem).FirstOrDefault();

                if (task == null)
                {
                    return false;
                }

                return task.EqualityCheck.HasFlag(EqualityCheck.CheckSum);
            }

            set
            {
                LibArchivumAeternum.Task task = Configuration.Tasks.Where(t => t.Name == Tasks.SelectedItem).FirstOrDefault();

                if (task != null)
                {
                    task.EqualityCheck = (task.EqualityCheck & ~EqualityCheck.CheckSum) | (value ? 0 : EqualityCheck.CheckSum);
                    NotifyPropertyChanged();
                }
            }
        }

        public BrowseButton BrowseButton { get; set; }
        public SaveButton SaveButton { get; set; }
        public StartSelectedTaskCommand StartSelectedTaskCommand { get; set; }
        public StartAllTasksCommand StartAllTasksCommand { get; set; }
        public ViewLogCommand ViewLogCommand { get; set; }

        public ListEditorViewModel Tasks
        {
            get
            {
                return _tasks;
            }

            set
            {
                if (_tasks != value)
                {
                    _tasks = value;
                    NotifyPropertyChanged();
                }
            }
        }

        public ListEditorViewModel Sources { get; set; }
        public ListEditorViewModel Excludes { get; set; }

        public Configuration Configuration
        {
            get
            {
                return _configuration;
            }

            private set
            {
                if (_configuration != value)
                {
                    _configuration = value;
                    NotifyPropertyChanged();
                }
            }
        }
        public ArchivumAeternum ArchivumAeternum { get; set; }

        public StringBuilder Log
        {
            get
            {
                return _log;
            }

            set
            {
                if (_log != value)
                {
                    _log = value;
                    NotifyPropertyChanged();
                }
            }
        }

        public MainWindowViewModel()
        {
            Sources = new ListEditorViewModel() { Title = "Sources:" };
            Excludes = new ListEditorViewModel() { Title = "Excludes:" };
            Tasks = new ListEditorViewModel() { Title = "Tasks:" };

            PropertyChanged += (sender, args) =>
            {
                switch (args.PropertyName)
                {
                    case "ConfigFile":
                        Configuration = LibArchivumAeternum.ConfigFile.Load(ConfigFile);

                        if (ArchivumAeternum != null)
                        {
                            ArchivumAeternum.OnStepStarted -= UpdateStatus;
                            ArchivumAeternum.OnStepFinished -= StepFinished;
                            ArchivumAeternum.OnStepFailed -= StepFailed;
                            ArchivumAeternum.OnBackupFinished -= BackupFinished;
                        }

                        ArchivumAeternum = new ArchivumAeternum(Configuration);

                        ArchivumAeternum.OnStepStarted += UpdateStatus;
                        ArchivumAeternum.OnStepFailed += StepFailed;
                        ArchivumAeternum.OnStepFinished += StepFinished;
                        ArchivumAeternum.OnBackupFinished += BackupFinished;

                        break;

                    case "Configuration":
                        Tasks.Items = Configuration.Tasks.Select(task => task.Name).ToList();
                        Tasks.SelectedItem = Tasks.Items == null ? null : Tasks.Items[0];
                        break;
                }
            };

            Tasks.PropertyChanged += (sender, args) =>
                {
                    switch (args.PropertyName)
                    {
                        case "SelectedItem":
                            LibArchivumAeternum.Task task = Configuration.Tasks.Where(t => t.Name == Tasks.SelectedItem).FirstOrDefault();

                            if (task != null)
                            {
                                Sources.Items = task.Sources;
                                Excludes.Items = task.Excludes == null ? null : task.Excludes.Select(e => e.Pattern).ToList();
                                NotifyPropertyChanged("Destination");
                                NotifyPropertyChanged("ComparisonByModificationDate");
                                NotifyPropertyChanged("ComparisonByFileSize");
                                NotifyPropertyChanged("ComparisonByCheckSum");
                            }
                            break;
                    }
                };

            BrowseButton = new BrowseButton(this);
            SaveButton = new SaveButton(this);
            StartSelectedTaskCommand = new StartSelectedTaskCommand(this);
            StartAllTasksCommand = new StartAllTasksCommand(this);
            ViewLogCommand = new ViewLogCommand(this);

            try
            {
                ConfigFile = GetConfigFileName();
                Configuration = LibArchivumAeternum.ConfigFile.Load(ConfigFile);
            }
            catch { }
        }

        private string GetConfigFileName()
        {
            try
            {
                RegistryKey key;
                key = Registry.CurrentUser.CreateSubKey("ArchivumAeternum");
                string configFile = (string)key.GetValue("ConfigFile");
                key.Close();
                return configFile;
            }
            catch
            {
                return null;
            }
        }

        private void UpdateStatus(object sender, string message)
        {
            if (message.EndsWith("...") || true)
            {
                Status = String.Format("[C{0} L{1} S{2} E{3}] {4}", copied, linked, skipped, errors, message);
            }

            _shadow_log.AppendFormat("{0}: {1}", DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss.fff"), message);

            if (message.StartsWith("Latest backup: ") || message.EndsWith("..."))
            {
                _shadow_log.AppendLine();
            }
        }

        private void StepFinished(object sender, string message)
        {
            switch (message)
            {
                case "copied":
                    copied++;
                    break;
                case "linked":
                    linked++;
                    break;
                case "skipped":
                    skipped++;
                    break;
            }

            _shadow_log.AppendLine(" " + message);
        }

        private void StepFailed(object sender, string message)
        {
            _shadow_log.AppendLine(" failed");
            _shadow_log.AppendFormat("{0}: {1}\r\n", DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss.fff"), message);
            errors++;
        }

        private void BackupFinished(object sender)
        {
            System.Windows.Application.Current.Dispatcher.Invoke(new Action(() =>
            {
                Log = _shadow_log;
            }));

            Status = String.Format("Copied: {0}, linked: {1}, skipped: {2}, errors: {3}", copied, linked, skipped, errors);
        }

        private void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void ResetCounters()
        {
            copied = 0;
            linked = 0;
            skipped = 0;
            errors = 0;
        }
    }

    public class BrowseButton : ICommand
    {
        private MainWindowViewModel _viewModel;

        public BrowseButton(MainWindowViewModel viewModel)
        {
            _viewModel = viewModel;
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public void Execute(object parameter)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();

            openFileDialog.Filter = "XML files (*.xml)|*.xml|All files (*.*)|*.*";
            openFileDialog.FilterIndex = 1;
            openFileDialog.RestoreDirectory = true;

            bool? value = openFileDialog.ShowDialog();

            if (value.HasValue && value.Value)
            {
                _viewModel.ConfigFile = openFileDialog.FileName;
            }
        }

        public event EventHandler CanExecuteChanged;
    }

    public class SaveButton : ICommand
    {
        MainWindowViewModel ViewModel;

        public SaveButton(MainWindowViewModel viewModel)
        {
            ViewModel = viewModel;

            ViewModel.PropertyChanged += (sender, args) =>
                {
                    if (args.PropertyName == "ConfigFile" && CanExecuteChanged != null)
                    {
                        CanExecuteChanged(this, new EventArgs());
                    }
                };
        }

        public bool CanExecute(object parameter)
        {
            return !String.IsNullOrWhiteSpace(ViewModel.ConfigFile);
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
        }
    }

    public class StartSelectedTaskCommand : ICommand
    {
        private MainWindowViewModel _viewModel;

        public StartSelectedTaskCommand(MainWindowViewModel viewModel)
        {
            _viewModel = viewModel;

            _viewModel.Tasks.PropertyChanged += (sender, args) =>
                {
                    if (args.PropertyName == "SelectedItem" && CanExecuteChanged != null)
                    {
                        CanExecuteChanged(this, new EventArgs());
                    }
                };
        }

        public bool CanExecute(object parameter)
        {
            return _viewModel.Tasks != null && _viewModel.Tasks.SelectedItem != null;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            _viewModel.ResetCounters();
            _viewModel.Log = null;
            _viewModel._shadow_log = new StringBuilder();

            if (_viewModel.Tasks != null && _viewModel.Tasks.Items != null && _viewModel.Tasks.SelectedItem != null)
            {
                try
                {
                    _viewModel.ArchivumAeternum.StartBackup(_viewModel.ArchivumAeternum.Tasks.FirstOrDefault(t => t.Name == _viewModel.Tasks.SelectedItem));
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }
    }

    public class StartAllTasksCommand : ICommand
    {
        private MainWindowViewModel _viewModel;
        System.Threading.Tasks.Task task;

        public StartAllTasksCommand(MainWindowViewModel viewModel)
        {
            _viewModel = viewModel;
            _viewModel.Log = null;
            _viewModel._shadow_log = new StringBuilder();

            _viewModel.Tasks.PropertyChanged += (sender, args) =>
                {
                    if (args.PropertyName == "SelectedItem" && CanExecuteChanged != null)
                    {
                        CanExecuteChanged(this, new EventArgs());
                    }
                };
        }

        public bool CanExecute(object parameter)
        {
            return _viewModel.Tasks != null && _viewModel.Tasks.Items != null && _viewModel.Tasks.Items.Count > 0;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            _viewModel.ResetCounters();

            task = new System.Threading.Tasks.Task(new Action(() =>
            {
                try
                {
                    _viewModel.ArchivumAeternum.StartBatchBackup();
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }));

            task.Start();
        }
    }

    public class ViewLogCommand : ICommand
    {
        private MainWindowViewModel _viewModel;
        System.Threading.Tasks.Task task;

        public ViewLogCommand(MainWindowViewModel viewModel)
        {
            _viewModel = viewModel;

            _viewModel.PropertyChanged += (sender, args) =>
            {
                if (args.PropertyName == "Log" && CanExecuteChanged != null)
                {
                    CanExecuteChanged(this, new EventArgs());
                }
            };
        }

        public bool CanExecute(object parameter)
        {
            return _viewModel.Log != null;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            string fileName = Guid.NewGuid().ToString() + ".txt";
            using (FileStream file = File.OpenWrite(fileName))
            {
                using (StreamWriter streamWriter = new StreamWriter(file))
                {
                    streamWriter.Write(_viewModel.Log.ToString());
                }
            }

            Process process = new Process();
            process.StartInfo = new ProcessStartInfo("notepad.exe", fileName);
            process.Start();
        }
    }
}
